/*
 * This file is part of algowala.
 *
 * algowala is copyright (c) 2012 Jagannathan Sampath <algowala@jagernot.com>
 *
 * algowala is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * algowala is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with algowala.  If not, see <http://www.gnu.org/licenses/>.
 *
*/

#ifndef __SOLVER
#define __SOLVER

#include "crvpt.h"
#include "multi_curve.h"
#include <vector>

struct solver;

struct xhandler {
  virtual void operator() (solver& s, double& x, double& dx) = 0;
  virtual ~xhandler () {}
};

struct atmin: xhandler {
  void operator() (solver& s, double& x, double& dx);
};

struct atmax: xhandler {
  void operator() (solver& s, double& x, double& dx);
};

struct tomin: xhandler {
  void operator() (solver& s, double& x, double& dx);
};

struct loopmin : atmin {
  void operator() (solver& s, double& x, double& dx);
};

struct loopmax : tomin {
  void operator() (solver& s, double& x, double& dx);
};

struct pongmax: atmax {
  void operator() (solver& s, double& x, double& dx);
};

struct pongmin: atmin {
  void operator() (solver& s, double& x, double& dx);
};

struct curve_editor;

extern atmin _atmin;
extern atmax _atmax;
extern tomin _tomin;
extern loopmin _loopmin;
extern loopmax _loopmax;
extern pongmin _pongmin;
extern pongmax _pongmax;

struct solver {

  //
  // given x return y
  //
  // assumes x such that
  // x0 <= x1 <= x2 <= ...... <= xlast
  //

  // solved curve
  multi_curve* mcrv;

  int ncurvs, icurv, last_curv;
  int iseg;

  // line segment on curve expected to contain x
  double startx, starty;
  double endx, endy;
  double m, inf;
  double ycomp;

  // first and last points
  double firstx, firsty;
  double lastx, lasty;

  double result; // last result

  inline int inseg (double x, int c, int i) {
    std::vector<curve>& curv = mcrv->curv;
    std::vector<crvpt>& vpts = curv[c].vpts;
    double leftx = vpts[i].x, rightx = vpts[i+1].x;

    if (x >= leftx && x <= rightx) {
        setseg (c, i);
        return 1;
    }
    return 0;
  }

  void setseg (int c, int i) {

    std::vector<curve>& curv = mcrv->curv;
    std::vector<crvpt>& vpts = curv[c].vpts;

    crvpt& start = vpts[i];
    crvpt& end = vpts[i+1];

    startx = start.x;
    starty = start.y;
    endx = end.x;
    endy = end.y;
    m = start.m;
    inf = start.inf;

    if (inf == 1) ycomp = endy; else ycomp = starty - m * startx;

    icurv = c;
    iseg = i;

  }

  inline int lastseg (int c) {
    std::vector<curve>& curv = mcrv->curv;
    std::vector<crvpt>& vpts = curv[c].vpts;
    return vpts.size() - 2;
    return 0;
  }

  inline int getsegs (int c) {
    return (mcrv->curv[c].vpts.size() - 1);
  }

  inline int searchleft (double x) {
    int lseg = iseg, lcurv = icurv;
    while (1) {
      if (--lseg < 0) {
        --lcurv;
        if (lcurv < 0) break;
        lseg = lastseg (lcurv);
      }
      if (inseg (x, lcurv, lseg)) return 1;
    }
    return 0;

  }

  inline int searchright (double x) {
    int rseg = iseg, rcurv = icurv;
    int rsegs = getsegs (rcurv);
    while (1) {
      if (++rseg < rsegs); else {
        ++rcurv;
        if (rcurv >= ncurvs) break;
        rsegs = getsegs (rcurv);
        rseg = 0;
      }
      if (inseg (x, rcurv, rseg)) return 1;
    }
    return 0;

  }

  inline int findseg (double x) {

    int fcurv = 0, fseg = 0, nsegs = getsegs (fcurv);
    while (1) {
      if (inseg (x, fcurv, fseg)) return 1; else {
        ++fseg;
        if (fseg >= nsegs) {
          if (++fcurv >= ncurvs) {
            setseg (ncurvs - 1, nsegs - 1);
            break;
          }
          fseg = 0;
          nsegs = getsegs (fcurv);
        }
      }
    }
    return 0;

  }

  solver ();
  solver (multi_curve* crv);

  void operator() (multi_curve* crv);

  // given x, solve y
  double operator() (double x);
  double operator() (double &x, double& dx, xhandler& xmin = _atmin, xhandler& xmax = _tomin);

  void operator() (double& x, double& dx, int n, double* soln, xhandler& xmin = _atmin, xhandler& xmax = _tomin); // fast solver when x is incremented by dx n times, solution stored in soln
  void operator() (double& x, double& dx, int n, double* mod, double* soln, xhandler& xmin = _atmin, xhandler& xmax = _tomin); // same as last solver but x is also modulated by mod.
  void operator() (double* ax, int n, xhandler& xmin = _atmin, xhandler& xmax = _atmax); // given an array of x, store solution in same array --> used by compressor

  void init ();
  
  void update (); // when multi curve has changed


};




#endif
